/*
 * Copyright (c) 2007 Universidade Federal de Campina Grande
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package br.edu.ufcg.threadcontrol;

import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.Map.Entry;

public class ThreadManager {

	private HashMap<String, HashMap<Thread, ThreadStateManager>> threadsByName;
	/**
	 * The following hash stores for each thread name a hash map with the
	 * possible states and the number of times they were achieved
	 */
	private HashMap<String, HashMap<String, Integer>> threadsByNameStateHistory;
	private HashMap<String, Set<Thread>> threadsByState;

	public ThreadManager() {
		super();
		this.reset();
	}

	public void reset() {
		threadsByName = new HashMap<String, HashMap<Thread, ThreadStateManager>>();
		threadsByState = new HashMap<String, Set<Thread>>();
		threadsByState.put(ThreadConfiguration.STATE_FINISHED,
				new LinkedHashSet<Thread>());
		threadsByState.put(ThreadConfiguration.STATE_RUNNING,
				new LinkedHashSet<Thread>());
		threadsByState.put(ThreadConfiguration.STATE_SLEEPING,
				new LinkedHashSet<Thread>());
		threadsByState.put(ThreadConfiguration.STATE_WAITING,
				new LinkedHashSet<Thread>());
		threadsByState.put(ThreadConfiguration.STATE_STARTED,
				new LinkedHashSet<Thread>());
		threadsByState.put(ThreadConfiguration.STATE_UNKNOWN,
				new LinkedHashSet<Thread>());
		threadsByState.put(ThreadConfiguration.STATE_NOTIFIED,
				new LinkedHashSet<Thread>());
		threadsByState.put(ThreadConfiguration.STATE_POSSIBLY_NOTIFIED,
				new LinkedHashSet<Thread>());
		this.threadsByNameStateHistory = new HashMap<String, HashMap<String, Integer>>();
	}

	public void addNewThreadStateHistory(String threadClassName, String state) {
		HashMap<String, Integer> threadHistory = this.threadsByNameStateHistory
				.get(threadClassName);
		if (threadHistory == null) {
			threadHistory = new HashMap<String, Integer>();
			threadHistory.put(ThreadConfiguration.STATE_FINISHED, 0);
			threadHistory.put(ThreadConfiguration.STATE_RUNNING, 0);
			threadHistory.put(ThreadConfiguration.STATE_SLEEPING, 0);
			threadHistory.put(ThreadConfiguration.STATE_WAITING, 0);
			threadHistory.put(ThreadConfiguration.STATE_STARTED, 0);
			threadHistory.put(ThreadConfiguration.STATE_UNKNOWN, 0);
			threadHistory.put(ThreadConfiguration.STATE_NOTIFIED, 0);
			threadHistory.put(ThreadConfiguration.STATE_POSSIBLY_NOTIFIED, 0);
			this.threadsByNameStateHistory.put(threadClassName, threadHistory);
		}
		int previousCounterValueOfState = threadHistory.get(state);
		threadHistory.put(state, previousCounterValueOfState + 1);
	}

	private int getNumberOfTimesInState(String className,
			Collection<String> stateNames) {
		HashMap<String, Integer> threadHistory = this.threadsByNameStateHistory
				.get(className);
		if (threadHistory == null) {
			return 0;
		} else {
			int timesInStates = 0;
			for (String s : stateNames) {
				timesInStates += threadHistory.get(s);
			}
			return timesInStates;
		}

	}

	public boolean add(Thread t, String state) {
		String className = this.getRunnableClassName(t);
		HashMap<Thread, ThreadStateManager> threadsWithName = this.threadsByName
				.get(className);
		ThreadStateManager tsm = new ThreadStateManager(t, state);
		if (threadsWithName == null) {
			threadsWithName = new HashMap<Thread, ThreadStateManager>();
			threadsWithName.put(t, tsm);
			this.threadsByName.put(className, threadsWithName);
		} else {
			threadsWithName.put(t, tsm);
		}
		this.threadsByState.get(state).add(t);
		this.addNewThreadStateHistory(className, state);
		return true;
	}

	public boolean remove(Thread t, String state) {
		HashMap<Thread, ThreadStateManager> threadsWithName = this.threadsByName
				.get(this.getRunnableClassName(t));
		ThreadStateManager removedTsm = null;
		if (threadsWithName != null) {
			removedTsm = threadsWithName.remove(t);
		}
		return removedTsm != null && this.threadsByState.get(state).remove(t);
	}// TODO: Check when the state is not valid

	public boolean changeState(Thread t, String fromState, String toState) {
		boolean removed = this.remove(t, fromState);
		this.add(t, toState);
		return removed;
	}

	public Set<Thread> getThreadsFromClass(String className) {
		HashMap<Thread, ThreadStateManager> threadsWithName = this.threadsByName
				.get(className);
		if (threadsWithName == null) {
			return new LinkedHashSet<Thread>();
		} else {
			return threadsWithName.keySet();
		}
	}

	public Set<Thread> getThreadsInState(String state) {
		Set<Thread> threadsInState = this.threadsByState.get(state);
		if (threadsInState == null) {
			return new LinkedHashSet<Thread>();
		} else {
			return threadsInState;
		}
	}

	public boolean isEmpty() {
		return this.threadsByName.isEmpty();
	}

	public boolean contains(Thread t, String state) {
		Set<Thread> threadsInState = this.threadsByState.get(state);
		return (threadsInState != null && threadsInState.contains(t));
	}

	public Iterator<Thread> iterator() {
		Set<Thread> allThreads = new LinkedHashSet<Thread>();
		for (Entry<String, HashMap<Thread, ThreadStateManager>> setThreads : this.threadsByName
				.entrySet()) {
			allThreads.addAll(setThreads.getValue().keySet());
		}
		return allThreads.iterator();
	}

	public boolean areThereThreadsInState(String state) {
		Set<Thread> threadsInState = this.threadsByState.get(state);
		if (threadsInState == null || threadsInState.size() == 0) {
			return false;
		}
		return true;
	}

	public boolean isThreadInState(String className,
			Collection<String> stateNames, int numTimes) {
		HashMap<Thread, ThreadStateManager> threadsWithClassName = this.threadsByName
				.get(className);
		if (threadsWithClassName == null || threadsWithClassName.size() == 0) {
			return false;
		} else {
			boolean isInState = false;
			for (ThreadStateManager tsm : threadsWithClassName.values()) {
				if (this.getRunnableClassName(tsm.getThread())
						.equals(className)) {
					isInState = stateNames.contains(tsm.getState());
					if (!isInState) {
						return false;
					}
				}
			}
			if (isInState) {
				if (numTimes == ThreadConfiguration.ANY_NUMBER_OF_TIMES) {
					return true;
				} else {
					return numTimes == this.getNumberOfTimesInState(className,
							stateNames);
				}
			}
			return isInState;
		}
	}

	public boolean areThereThreadsRunning() {
		return areThereThreadsInState(ThreadConfiguration.STATE_RUNNING);
	}

	public boolean areThereThreadsWaiting() {
		return areThereThreadsInState(ThreadConfiguration.STATE_WAITING);
	}

	public boolean areThereThreadsFinished() {
		return areThereThreadsInState(ThreadConfiguration.STATE_FINISHED);
	}

	public boolean areThereThreadsSleping() {
		return areThereThreadsInState(ThreadConfiguration.STATE_SLEEPING);
	}

	public boolean areThereThreadsStarted() {
		return areThereThreadsInState(ThreadConfiguration.STATE_STARTED);
	}

	public boolean isThreadInState(Thread t, String state) {
		Set<Thread> ts = this.threadsByState.get(state);
		if (ts == null || ts.size() == 0) {
			return false;
		} else {
			return ts.contains(t);
		}
	}

	private HashMap<Thread, String> classNamesOfRunnables = new HashMap<Thread, String>();

	public String getRunnableClassName(Thread t) {
		String className = this.classNamesOfRunnables.get(t);
		if (className == null) {
			className = t.getClass().getName();
		}
		return className;
	}

	public boolean addRunnableClassName(Thread t, String className) {
		if (!this.classNamesOfRunnables.containsKey(t)) {
			String previousName = getRunnableClassName(t);
			if (previousName.equals(className)) {
				this.classNamesOfRunnables.put(t, className);
			} else {
				this.remove(t, ThreadConfiguration.STATE_STARTED);
				this.classNamesOfRunnables.put(t, className);
				this.add(t, ThreadConfiguration.STATE_STARTED);
				return true;
			}
		}
		return false;
	}

	public String getThreadState(Thread t) {
		ThreadStateManager tsm = this.threadsByName.get(
				this.getRunnableClassName(t)).get(t);
		if (tsm != null) {
			return tsm.getState();
		} else {
			return ThreadConfiguration.STATE_UNKNOWN;
		}
	}

	public boolean changeToState(Thread t, String newState) {
		String state = this.getThreadState(t);
		if (!state.equals(newState)) {
			this.changeState(t, state, newState);
			return true;
		}
		return false;
	}

}
